package errorx

import (
	"bytes"
	"fmt"
	"io"
	"runtime"
)

type StackError struct {
	stack []uintptr
	error error
}

func (s StackError) Error() string {
	bf := bytes.NewBuffer(make([]byte, 256))
	bf.Reset()
	s.encode(bf)
	return bf.String()
}

func New(text string) *StackError {
	stack := make([]uintptr, 32)
	stackLen := runtime.Callers(2, stack)
	return &StackError{
		stack: stack[:stackLen],
		error: ConstStringErr(text),
	}
}

func Newf(format string, args ...any) *StackError {
	stack := make([]uintptr, 32)
	stackLen := runtime.Callers(2, stack)
	return &StackError{
		stack: stack[:stackLen],
		error: fmt.Errorf(format, args...),
	}
}

func (s StackError) Format(f fmt.State, verb rune) {
	s.encode(f)
}

func (s StackError) encode(f io.Writer) {
	io.WriteString(f, s.error.Error())
	io.WriteString(f, "\n")
	for i := 0; i <= len(s.stack)-1; i++ {
		pc := s.stack[i]
		funcForPC := runtime.FuncForPC(pc)
		if funcForPC == nil {
			continue
		}
		file, line := funcForPC.FileLine(pc)
		name := funcForPC.Name()
		realS := fmt.Sprintf("%s\n\t%s:%d\n", name, file, line)
		io.WriteString(f, realS)
	}
}
